﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.Extensions.Internal;

namespace Microsoft.AspNetCore.Razor.Language.Syntax;

internal readonly struct ChildSyntaxList : IEquatable<ChildSyntaxList>, IReadOnlyList<SyntaxNode>
{
    private readonly SyntaxNode _node;
    private readonly int _count;

    internal ChildSyntaxList(SyntaxNode node)
    {
        _node = node;
        _count = CountNodes(node.Green);
    }

    /// <summary>
    /// Gets the number of children contained in the <see cref="ChildSyntaxList"/>.
    /// </summary>
    public int Count
    {
        get
        {
            return _count;
        }
    }

    internal static int CountNodes(GreenNode green)
    {
        var n = 0;

        for (int i = 0, s = green.SlotCount; i < s; i++)
        {
            var child = green.GetSlot(i);
            if (child != null)
            {
                if (!child.IsList)
                {
                    n++;
                }
                else
                {
                    n += child.SlotCount;
                }
            }
        }

        return n;
    }

    /// <summary>Gets the child at the specified index.</summary>
    /// <param name="index">The zero-based index of the child to get.</param>
    /// <exception cref="System.ArgumentOutOfRangeException">
    ///   <paramref name="index"/> is less than 0.-or-<paramref name="index" /> is equal to or greater than <see cref="ChildSyntaxList.Count"/>. </exception>
    public SyntaxNode this[int index]
    {
        get
        {
            if (unchecked((uint)index < (uint)_count))
            {
                return ItemInternal(_node, index);
            }

            throw new ArgumentOutOfRangeException(nameof(index));
        }
    }

    internal SyntaxNode Node
    {
        get { return _node; }
    }

    private static int Occupancy(GreenNode green)
    {
        return green.IsList ? green.SlotCount : 1;
    }

    /// <summary>
    /// internal indexer that does not verify index.
    /// Used when caller has already ensured that index is within bounds.
    /// </summary>
    internal static SyntaxNode ItemInternal(SyntaxNode node, int index)
    {
        GreenNode greenChild;
        var green = node.Green;
        var idx = index;
        var slotIndex = 0;
        var position = node.Position;

        // find a slot that contains the node or its parent list (if node is in a list)
        // we will be skipping whole slots here so we will not loop for long
        //
        // at the end of this loop we will have
        // 1) slot index - slotIdx
        // 2) if the slot is a list, node index in the list - idx
        // 3) slot position - position
        while (true)
        {
            greenChild = green.GetSlot(slotIndex);
            if (greenChild != null)
            {
                var currentOccupancy = Occupancy(greenChild);
                if (idx < currentOccupancy)
                {
                    break;
                }

                idx -= currentOccupancy;
                position += greenChild.FullWidth;
            }

            slotIndex++;
        }

        // get node that represents this slot
        var red = node.GetNodeSlot(slotIndex);
        if (!greenChild.IsList)
        {
            // this is a single node
            // if it is a node, we are done
            if (red != null)
            {
                return red;
            }
        }
        else if (red != null)
        {
            // it is a red list of nodes (separated or not), most common case
            var redChild = red.GetNodeSlot(idx);
            if (redChild != null)
            {
                // this is our node
                return redChild;
            }
        }

        return node;
    }

    /// <summary>
    /// Locate the node that is a child of the given <see cref="SyntaxNode"/> and contains the given position.
    /// </summary>
    /// <param name="node">The <see cref="SyntaxNode"/> to search.</param>
    /// <param name="targetPosition">The position.</param>
    /// <returns>The node that spans the given position.</returns>
    /// <remarks>
    /// Assumes that <paramref name="targetPosition"/> is within the span of <paramref name="node"/>.
    /// </remarks>
    internal static SyntaxNode ChildThatContainsPosition(SyntaxNode node, int targetPosition)
    {
        // The targetPosition must already be within this node
        Debug.Assert(node.FullSpan.Contains(targetPosition));

        var green = node.Green;
        var position = node.Position;
        var index = 0;

        Debug.Assert(!green.IsList);

        // Find the green node that spans the target position.
        // We will be skipping whole slots here so we will not loop for long
        int slot;
        for (slot = 0; ; slot++)
        {
            var greenChild = green.GetSlot(slot);
            if (greenChild != null)
            {
                var endPosition = position + greenChild.FullWidth;
                if (targetPosition < endPosition)
                {
                    // Descend into the child element
                    green = greenChild;
                    break;
                }

                position = endPosition;
                index += Occupancy(greenChild);
            }
        }

        // Realize the red node (if any)
        var red = node.GetNodeSlot(slot);
        if (!green.IsList)
        {
            // This is a single node.
            // If it is a node, we are done.
            if (red != null)
            {
                return red;
            }
        }
        else
        {
            slot = green.FindSlotIndexContainingOffset(targetPosition - position);

            // Realize the red node (if any)
            if (red != null)
            {
                // It is a red list of nodes
                red = red.GetNodeSlot(slot);
                if (red != null)
                {
                    return red;
                }
            }

            // Since we can't have "lists of lists", the Occupancy calculation for
            // child elements in a list is simple.
            index += slot;
        }

        return node;
    }

    /// <summary>
    /// internal indexer that does not verify index.
    /// Used when caller has already ensured that index is within bounds.
    /// </summary>
    internal static SyntaxNode ItemInternalAsNode(SyntaxNode node, int index)
    {
        GreenNode greenChild;
        var green = node.Green;
        var idx = index;
        var slotIndex = 0;

        // find a slot that contains the node or its parent list (if node is in a list)
        // we will be skipping whole slots here so we will not loop for long
        //
        // at the end of this loop we will have
        // 1) slot index - slotIdx
        // 2) if the slot is a list, node index in the list - idx
        while (true)
        {
            greenChild = green.GetSlot(slotIndex);
            if (greenChild != null)
            {
                var currentOccupancy = Occupancy(greenChild);
                if (idx < currentOccupancy)
                {
                    break;
                }

                idx -= currentOccupancy;
            }

            slotIndex++;
        }

        // get node that represents this slot
        var red = node.GetNodeSlot(slotIndex);
        if (greenChild.IsList && red != null)
        {
            // it is a red list of nodes, most common case
            return red.GetNodeSlot(idx);
        }

        // this is a single node
        return red;
    }

    // for debugging
    private SyntaxNode[] Nodes
    {
        get
        {
            return this.ToArray();
        }
    }

    public bool Any()
    {
        return _count != 0;
    }

    /// <summary>
    /// Returns the first child in the list.
    /// </summary>
    /// <returns>The first child in the list.</returns>
    /// <exception cref="System.InvalidOperationException">The list is empty.</exception>
    public SyntaxNode First()
    {
        if (Any())
        {
            return this[0];
        }

        throw new InvalidOperationException();
    }

    /// <summary>
    /// Returns the last child in the list.
    /// </summary>
    /// <returns>The last child in the list.</returns>
    /// <exception cref="System.InvalidOperationException">The list is empty.</exception>
    public SyntaxNode Last()
    {
        if (Any())
        {
            return this[_count - 1];
        }

        throw new InvalidOperationException();
    }

    /// <summary>
    /// Returns a list which contains all children of <see cref="ChildSyntaxList"/> in reversed order.
    /// </summary>
    /// <returns><see cref="Reversed"/> which contains all children of <see cref="ChildSyntaxList"/> in reversed order</returns>
    public Reversed Reverse()
    {
        return new Reversed(_node, _count);
    }

    /// <summary>Returns an enumerator that iterates through the <see cref="ChildSyntaxList"/>.</summary>
    /// <returns>A <see cref="Enumerator"/> for the <see cref="ChildSyntaxList"/>.</returns>
    public Enumerator GetEnumerator()
    {
        if (_node == null)
        {
            return default;
        }

        return new Enumerator(_node, _count);
    }

    IEnumerator<SyntaxNode> IEnumerable<SyntaxNode>.GetEnumerator()
    {
        if (_node == null)
        {
            return EmptyEnumerator<SyntaxNode>.Instance;
        }

        return new EnumeratorImpl(_node, _count);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        if (_node == null)
        {
            return EmptyEnumerator<SyntaxNode>.Instance;
        }

        return new EnumeratorImpl(_node, _count);
    }

    /// <summary>Determines whether the specified object is equal to the current instance.</summary>
    /// <returns>true if the specified object is a <see cref="ChildSyntaxList" /> structure and is equal to the current instance; otherwise, false.</returns>
    /// <param name="obj">The object to be compared with the current instance.</param>
    public override bool Equals(object obj)
    {
        return obj is ChildSyntaxList && Equals((ChildSyntaxList)obj);
    }

    /// <summary>Determines whether the specified <see cref="ChildSyntaxList" /> structure is equal to the current instance.</summary>
    /// <returns>true if the specified <see cref="ChildSyntaxList" /> structure is equal to the current instance; otherwise, false.</returns>
    /// <param name="other">The <see cref="ChildSyntaxList" /> structure to be compared with the current instance.</param>
    public bool Equals(ChildSyntaxList other)
    {
        return _node == other._node;
    }

    /// <summary>Returns the hash code for the current instance.</summary>
    /// <returns>A 32-bit signed integer hash code.</returns>
    public override int GetHashCode()
    {
        return _node?.GetHashCode() ?? 0;
    }

    /// <summary>Indicates whether two <see cref="ChildSyntaxList" /> structures are equal.</summary>
    /// <returns>true if <paramref name="list1" /> is equal to <paramref name="list2" />; otherwise, false.</returns>
    /// <param name="list1">The <see cref="ChildSyntaxList" /> structure on the left side of the equality operator.</param>
    /// <param name="list2">The <see cref="ChildSyntaxList" /> structure on the right side of the equality operator.</param>
    public static bool operator ==(ChildSyntaxList list1, ChildSyntaxList list2)
    {
        return list1.Equals(list2);
    }

    /// <summary>Indicates whether two <see cref="ChildSyntaxList" /> structures are unequal.</summary>
    /// <returns>true if <paramref name="list1" /> is equal to <paramref name="list2" />; otherwise, false.</returns>
    /// <param name="list1">The <see cref="ChildSyntaxList" /> structure on the left side of the inequality operator.</param>
    /// <param name="list2">The <see cref="ChildSyntaxList" /> structure on the right side of the inequality operator.</param>
    public static bool operator !=(ChildSyntaxList list1, ChildSyntaxList list2)
    {
        return !list1.Equals(list2);
    }

    /// <summary>Enumerates the elements of a <see cref="ChildSyntaxList" />.</summary>
    public struct Enumerator
    {
        private SyntaxNode _node;
        private int _count;
        private int _childIndex;

        internal Enumerator(SyntaxNode node, int count)
        {
            _node = node;
            _count = count;
            _childIndex = -1;
        }

        // PERF: Initialize an Enumerator directly from a SyntaxNode without going
        // via ChildNodes. This saves constructing an intermediate ChildSyntaxList
        internal void InitializeFrom(SyntaxNode node)
        {
            _node = node;
            _count = CountNodes(node.Green);
            _childIndex = -1;
        }

        /// <summary>Advances the enumerator to the next element of the <see cref="ChildSyntaxList" />.</summary>
        /// <returns>true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.</returns>
        public bool MoveNext()
        {
            var newIndex = _childIndex + 1;
            if (newIndex < _count)
            {
                _childIndex = newIndex;
                return true;
            }

            return false;
        }

        /// <summary>Gets the element at the current position of the enumerator.</summary>
        /// <returns>The element in the <see cref="ChildSyntaxList" /> at the current position of the enumerator.</returns>
        public SyntaxNode Current
        {
            get
            {
                return ItemInternal(_node, _childIndex);
            }
        }

        /// <summary>Sets the enumerator to its initial position, which is before the first element in the collection.</summary>
        public void Reset()
        {
            _childIndex = -1;
        }

        internal bool TryMoveNextAndGetCurrent(out SyntaxNode current)
        {
            if (!MoveNext())
            {
                current = default;
                return false;
            }

            current = ItemInternal(_node, _childIndex);
            return true;
        }

        internal SyntaxNode TryMoveNextAndGetCurrentAsNode()
        {
            while (MoveNext())
            {
                var nodeValue = ItemInternalAsNode(_node, _childIndex);
                if (nodeValue != null)
                {
                    return nodeValue;
                }
            }

            return null;
        }
    }

    private class EnumeratorImpl : IEnumerator<SyntaxNode>
    {
        private Enumerator _enumerator;

        internal EnumeratorImpl(SyntaxNode node, int count)
        {
            _enumerator = new Enumerator(node, count);
        }

        /// <summary>
        /// Gets the element in the collection at the current position of the enumerator.
        /// </summary>
        /// <returns>
        /// The element in the collection at the current position of the enumerator.
        ///   </returns>
        public SyntaxNode Current
        {
            get { return _enumerator.Current; }
        }

        /// <summary>
        /// Gets the element in the collection at the current position of the enumerator.
        /// </summary>
        /// <returns>
        /// The element in the collection at the current position of the enumerator.
        ///   </returns>
        object IEnumerator.Current
        {
            get { return _enumerator.Current; }
        }

        /// <summary>
        /// Advances the enumerator to the next element of the collection.
        /// </summary>
        /// <returns>
        /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
        /// </returns>
        public bool MoveNext()
        {
            return _enumerator.MoveNext();
        }

        /// <summary>
        /// Sets the enumerator to its initial position, which is before the first element in the collection.
        /// </summary>
        public void Reset()
        {
            _enumerator.Reset();
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        { }
    }

    public readonly partial struct Reversed : IEnumerable<SyntaxNode>, IEquatable<Reversed>
    {
        private readonly SyntaxNode _node;
        private readonly int _count;

        internal Reversed(SyntaxNode node, int count)
        {
            _node = node;
            _count = count;
        }

        public Enumerator GetEnumerator()
        {
            return new Enumerator(_node, _count);
        }

        IEnumerator<SyntaxNode> IEnumerable<SyntaxNode>.GetEnumerator()
        {
            if (_node == null)
            {
                return EmptyEnumerator<SyntaxNode>.Instance;
            }

            return new EnumeratorImpl(_node, _count);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            if (_node == null)
            {
                return EmptyEnumerator<SyntaxNode>.Instance;
            }

            return new EnumeratorImpl(_node, _count);
        }

        public override int GetHashCode()
        {
            if (_node == null)
            {
                return 0;
            }

            var hash = HashCodeCombiner.Start();
            hash.Add(_node.GetHashCode());
            hash.Add(_count);
            return hash.CombinedHash;
        }

        public override bool Equals(object obj)
        {
            return (obj is Reversed) && Equals((Reversed)obj);
        }

        public bool Equals(Reversed other)
        {
            return _node == other._node
                && _count == other._count;
        }

        public struct Enumerator
        {
            private readonly SyntaxNode _node;
            private readonly int _count;
            private int _childIndex;

            internal Enumerator(SyntaxNode node, int count)
            {
                _node = node;
                _count = count;
                _childIndex = count;
            }

            public bool MoveNext()
            {
                return --_childIndex >= 0;
            }

            public SyntaxNode Current
            {
                get
                {
                    return ItemInternal(_node, _childIndex);
                }
            }

            public void Reset()
            {
                _childIndex = _count;
            }
        }

        private class EnumeratorImpl : IEnumerator<SyntaxNode>
        {
            private Enumerator _enumerator;

            internal EnumeratorImpl(SyntaxNode node, int count)
            {
                _enumerator = new Enumerator(node, count);
            }

            /// <summary>
            /// Gets the element in the collection at the current position of the enumerator.
            /// </summary>
            /// <returns>
            /// The element in the collection at the current position of the enumerator.
            ///   </returns>
            public SyntaxNode Current
            {
                get { return _enumerator.Current; }
            }

            /// <summary>
            /// Gets the element in the collection at the current position of the enumerator.
            /// </summary>
            /// <returns>
            /// The element in the collection at the current position of the enumerator.
            ///   </returns>
            object IEnumerator.Current
            {
                get { return _enumerator.Current; }
            }

            /// <summary>
            /// Advances the enumerator to the next element of the collection.
            /// </summary>
            /// <returns>
            /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
            /// </returns>
            /// <exception cref="InvalidOperationException">The collection was modified after the enumerator was created. </exception>
            public bool MoveNext()
            {
                return _enumerator.MoveNext();
            }

            /// <summary>
            /// Sets the enumerator to its initial position, which is before the first element in the collection.
            /// </summary>
            /// <exception cref="InvalidOperationException">The collection was modified after the enumerator was created. </exception>
            public void Reset()
            {
                _enumerator.Reset();
            }

            /// <summary>
            /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
            /// </summary>
            public void Dispose()
            { }
        }
    }

    internal class EmptyEnumerator<T> : IEnumerator<T>
    {
        public static readonly IEnumerator<T> Instance = new EmptyEnumerator<T>();

        protected EmptyEnumerator()
        {
        }

        public T Current => throw new InvalidOperationException();

        object IEnumerator.Current => throw new NotImplementedException();

        public void Dispose()
        {
        }

        public bool MoveNext()
        {
            return false;
        }

        public void Reset()
        {
            throw new NotImplementedException();
        }
    }
}
